Fix build scripts and double-compiled packages
authorAlex Crichton <alex@alexcrichton.com>
Fri, 7 Nov 2014 19:09:59 +0000 (11:09 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Fri, 7 Nov 2014 19:22:53 +0000 (11:22 -0800)
This commit fixes support for build scripts in packages which are compiled for
both the host and target architectures. The support was previously hindered by
the fact that the build script was always invoked precisely once for the target
architecture unconditionally.

This adds support for build scripts themselves to depend on build scripts, and
everything should "just work" if build scripts respect their environment
variables.

src/cargo/ops/cargo_clean.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_rustc/context.rs
src/cargo/ops/cargo_rustc/custom_build.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/ops/mod.rs
src/cargo/util/config.rs
src/doc/build-script.md
tests/test_cargo_compile_custom_build.rs
tests/test_cargo_cross_compile.rs

index 7b3aa0ae6df567cccf86d76087cc63c0f049631c..4474302ba22e7d27b707cd9ef6cd4488a0cc2674 100644 (file)
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use std::default::Default;
 use std::io::fs::{mod, PathExtensions};
 
 use core::{MultiShell, PackageSet};
@@ -50,7 +50,7 @@ pub fn clean(manifest_path: &Path, opts: &mut CleanOptions) -> CargoResult<()> {
     let pkgs = PackageSet::new([]);
     let cx = try!(Context::new("compile", &resolve, &srcs, &pkgs, &mut cfg,
                                Layout::at(root.get_absolute_target_dir()),
-                               None, &pkg, HashMap::new()));
+                               None, &pkg, Default::default()));
 
     // And finally, clean everything out!
     for target in pkg.get_targets().iter() {
index d05685770c1343b872c26c64c0de6afc737832ba..1de7a9e50cbac8a1d1f0d65a5aeff7249b41e9ab 100644 (file)
@@ -24,6 +24,7 @@
 
 use std::os;
 use std::collections::HashMap;
+use std::default::Default;
 
 use core::registry::PackageRegistry;
 use core::{MultiShell, Source, SourceId, PackageSet, Package, Target, PackageId};
@@ -138,7 +139,7 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions)
 
     let ret = {
         let _p = profile::start("compiling");
-        let lib_overrides = try!(scrape_target_config(&config, &user_configs));
+        let lib_overrides = try!(scrape_build_config(&config, &user_configs));
 
         try!(ops::compile_targets(env.as_slice(), targets.as_slice(), to_build,
                                   &PackageSet::new(packages.as_slice()),
@@ -173,25 +174,40 @@ fn source_ids_from_config(configs: &HashMap<String, config::ConfigValue>,
     }).map(|p| SourceId::for_path(&p)).collect()
 }
 
-fn scrape_target_config(config: &Config,
-                        configs: &HashMap<String, config::ConfigValue>)
-                        -> CargoResult<HashMap<String, BuildOutput>> {
+fn scrape_build_config(config: &Config,
+                       configs: &HashMap<String, config::ConfigValue>)
+                       -> CargoResult<ops::BuildConfig> {
     let target = match configs.find_equiv("target") {
-        None => return Ok(HashMap::new()),
+        None => return Ok(Default::default()),
         Some(target) => try!(target.table().chain_error(|| {
             internal("invalid configuration for the key `target`")
         })),
     };
-    let triple = config.target().unwrap_or(config.rustc_host()).to_string();
-    let target = match target.get(&triple) {
-        None => return Ok(HashMap::new()),
+
+    let host = try!(scrape_target_config(target, config.rustc_host()));
+    let target = match config.target() {
+        Some(triple) => try!(scrape_target_config(target, triple)),
+        None => host.clone(),
+    };
+    Ok(ops::BuildConfig { host: host, target: target })
+}
+
+fn scrape_target_config(target: &HashMap<String, config::ConfigValue>,
+                        triple: &str)
+                        -> CargoResult<ops::TargetConfig> {
+    let target = match target.get(&triple.to_string()) {
+        None => return Ok(Default::default()),
         Some(target) => try!(target.table().chain_error(|| {
             internal(format!("invalid configuration for the key \
                               `target.{}`", triple))
         })),
     };
 
-    let mut ret = HashMap::new();
+    let mut ret = ops::TargetConfig {
+        ar: None,
+        linker: None,
+        overrides: HashMap::new(),
+    };
     for (k, v) in target.iter() {
         match k.as_slice() {
             "ar" | "linker" => {
@@ -199,9 +215,9 @@ fn scrape_target_config(config: &Config,
                     internal(format!("invalid configuration for key `{}`", k))
                 })).ref0().to_string();
                 if k.as_slice() == "linker" {
-                    config.set_linker(v);
+                    ret.linker = Some(v);
                 } else {
-                    config.set_ar(v);
+                    ret.ar = Some(v);
                 }
             }
             lib_name => {
@@ -233,7 +249,7 @@ fn scrape_target_config(config: &Config,
                         output.metadata.push((k.to_string(), v.to_string()));
                     }
                 }
-                ret.insert(lib_name.to_string(), output);
+                ret.overrides.insert(lib_name.to_string(), output);
             }
         }
     }
index 5e9207aa91f457cec4b544032a0426a6861d70ae..89e526308ef5087aae7b00923cea4946949e6b7d 100644 (file)
@@ -7,7 +7,8 @@ use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target};
 use util::{mod, CargoResult, ChainError, internal, Config, profile};
 use util::human;
 
-use super::{Kind, KindHost, KindTarget, Compilation, BuildOutput};
+use super::{Kind, KindHost, KindTarget, Compilation, BuildConfig};
+use super::TargetConfig;
 use super::layout::{Layout, LayoutProxy};
 use super::custom_build::BuildState;
 
@@ -34,6 +35,7 @@ pub struct Context<'a, 'b: 'a> {
     target_dylib: Option<(String, String)>,
     target_exe: String,
     requirements: HashMap<(&'a PackageId, &'a str), PlatformRequirement>,
+    build_config: BuildConfig,
 }
 
 impl<'a, 'b: 'a> Context<'a, 'b> {
@@ -41,7 +43,7 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
                deps: &'a PackageSet, config: &'b Config<'b>,
                host: Layout, target: Option<Layout>,
                root_pkg: &Package,
-               build_state: HashMap<String, BuildOutput>)
+               build_config: BuildConfig)
                -> CargoResult<Context<'a, 'b>> {
         let (target_dylib, target_exe) =
                 try!(Context::filename_parts(config.target()));
@@ -67,7 +69,8 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
             host_dylib: host_dylib,
             requirements: HashMap::new(),
             compilation: Compilation::new(root_pkg),
-            build_state: Arc::new(BuildState::new(build_state, deps)),
+            build_state: Arc::new(BuildState::new(build_config.clone(), deps)),
+            build_config: build_config,
         })
     }
 
@@ -165,8 +168,13 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
 
     pub fn get_requirement(&self, pkg: &'a Package,
                            target: &'a Target) -> PlatformRequirement {
+        let default = if target.get_profile().is_for_host() {
+            PlatformPlugin
+        } else {
+            PlatformTarget
+        };
         self.requirements.get(&(pkg.get_package_id(), target.get_name()))
-            .map(|a| *a).unwrap_or(PlatformTarget)
+            .map(|a| *a).unwrap_or(default)
     }
 
     /// Returns the appropriate directory layout for either a plugin or not.
@@ -276,6 +284,24 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
                  !target.get_profile().is_test(),
         }
     }
+
+    /// Get the user-specified linker for a particular host or target
+    pub fn linker(&self, kind: Kind) -> Option<&str> {
+        self.target_config(kind).linker.as_ref().map(|s| s.as_slice())
+    }
+
+    /// Get the user-specified `ar` program for a particular host or target
+    pub fn ar(&self, kind: Kind) -> Option<&str> {
+        self.target_config(kind).ar.as_ref().map(|s| s.as_slice())
+    }
+
+    /// Get the target configuration for a particular host or target
+    fn target_config(&self, kind: Kind) -> &TargetConfig {
+        match kind {
+            KindHost => &self.build_config.host,
+            KindTarget => &self.build_config.target,
+        }
+    }
 }
 
 impl PlatformRequirement {
index e5d3d78595561f1c3b839ce734334cc9562cca01..342f67e11068e0c448646798b18d226072b0176f 100644 (file)
@@ -10,7 +10,7 @@ use util::{CargoResult, CargoError, human};
 use util::{internal, ChainError, Require};
 
 use super::job::Work;
-use super::{fingerprint, process, KindTarget, KindHost, Context};
+use super::{fingerprint, process, KindTarget, KindHost, Kind, Context};
 use util::Freshness;
 
 /// Contains the parsed output of a custom build script.
@@ -25,16 +25,16 @@ pub struct BuildOutput {
 }
 
 pub struct BuildState {
-    pub outputs: Mutex<HashMap<PackageId, BuildOutput>>,
+    pub outputs: Mutex<HashMap<(PackageId, Kind), BuildOutput>>,
 }
 
 /// Prepares a `Work` that executes the target as a custom build script.
-pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
+pub fn prepare(pkg: &Package, target: &Target, kind: Kind, cx: &mut Context)
                -> CargoResult<(Work, Work, Freshness)> {
     // TODO: this shouldn't explicitly pass `KindTarget` for the layout, we
     //       may be running a build script for a plugin dependency.
     let (script_output, old_script_output, build_output, old_build_output) = {
-        let target = cx.layout(pkg, KindTarget);
+        let target = cx.layout(pkg, kind);
         let host = cx.layout(pkg, KindHost);
         (host.build(pkg),
          host.proxy().old_build(pkg),
@@ -55,7 +55,10 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
                                                         .dir_path()
                                                         .display().to_string()))
                      .env("NUM_JOBS", Some(cx.config.jobs().to_string()))
-                     .env("TARGET", Some(cx.target_triple()))
+                     .env("TARGET", Some(match kind {
+                         KindHost => cx.config.rustc_host(),
+                         KindTarget => cx.target_triple(),
+                     }))
                      .env("DEBUG", Some(profile.get_debug().to_string()))
                      .env("OPT_LEVEL", Some(profile.get_opt_level().to_string()))
                      .env("PROFILE", Some(profile.get_env()));
@@ -123,7 +126,8 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
         {
             let build_state = build_state.outputs.lock();
             for &(ref name, ref id) in lib_deps.iter() {
-                for &(ref key, ref value) in (*build_state)[*id].metadata.iter() {
+                let data = &build_state[(id.clone(), kind)].metadata;
+                for &(ref key, ref value) in data.iter() {
                     p = p.env(format!("DEP_{}_{}",
                                       super::envify(name.as_slice()),
                                       super::envify(key.as_slice())).as_slice(),
@@ -151,7 +155,7 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
             human("build script output was not valid utf-8")
         }));
         let build_output = try!(BuildOutput::parse(output, pkg_name.as_slice()));
-        build_state.outputs.lock().insert(id, build_output);
+        build_state.outputs.lock().insert((id, kind), build_output);
 
         try!(File::create(&script_output.join("output"))
                   .write_str(output).map_err(|e| {
@@ -186,7 +190,7 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
         let contents = try!(f.read_to_string());
         let output = try!(BuildOutput::parse(contents.as_slice(),
                                              pkg_name.as_slice()));
-        build_state.outputs.lock().insert(id, output);
+        build_state.outputs.lock().insert((id, kind), output);
 
         fresh(tx)
     };
@@ -195,7 +199,7 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
 }
 
 impl BuildState {
-    pub fn new(overrides: HashMap<String, BuildOutput>,
+    pub fn new(config: super::BuildConfig,
                packages: &PackageSet) -> BuildState {
         let mut sources = HashMap::new();
         for package in packages.iter() {
@@ -208,8 +212,11 @@ impl BuildState {
             }
         }
         let mut outputs = HashMap::new();
-        for (name, output) in overrides.into_iter() {
-            outputs.insert(sources[name].clone(), output);
+        for (name, output) in config.host.overrides.into_iter() {
+            outputs.insert((sources[name].clone(), KindHost), output);
+        }
+        for (name, output) in config.target.overrides.into_iter() {
+            outputs.insert((sources[name].clone(), KindTarget), output);
         }
         BuildState { outputs: Mutex::new(outputs) }
     }
index ad7ef689066108da4836ea497a794d8ac63774f0..6c461135e73879022bd619cada4eb7cbac19a971 100644 (file)
@@ -27,9 +27,22 @@ mod job_queue;
 mod layout;
 mod links;
 
-#[deriving(PartialEq, Eq)]
+#[deriving(PartialEq, Eq, Hash, Show)]
 pub enum Kind { KindHost, KindTarget }
 
+#[deriving(Default, Clone)]
+pub struct BuildConfig {
+    pub host: TargetConfig,
+    pub target: TargetConfig,
+}
+
+#[deriving(Clone, Default)]
+pub struct TargetConfig {
+    pub ar: Option<String>,
+    pub linker: Option<String>,
+    pub overrides: HashMap<String, BuildOutput>,
+}
+
 /// Run `rustc` to figure out what its current version string is.
 ///
 /// The second element of the tuple returned is the target triple that rustc
@@ -76,7 +89,7 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
                            deps: &PackageSet, resolve: &'a Resolve,
                            sources: &'a SourceMap,
                            config: &'a Config<'a>,
-                           lib_overrides: HashMap<String, BuildOutput>)
+                           build_config: BuildConfig)
                            -> CargoResult<Compilation> {
     if targets.is_empty() {
         return Ok(Compilation::new(pkg))
@@ -95,7 +108,7 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
 
     let mut cx = try!(Context::new(env, resolve, sources, deps, config,
                                    host_layout, target_layout, pkg,
-                                   lib_overrides));
+                                   build_config));
     let mut queue = JobQueue::new(cx.resolve, deps, cx.config);
 
     // First ensure that the destination directory exists
@@ -170,18 +183,6 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
             (Vec::new(), Vec::new(), Vec::new(), Vec::new());
     let (mut build_custom, mut run_custom) = (Vec::new(), Vec::new());
     for &target in targets.iter() {
-        if target.get_profile().is_custom_build() {
-            // Custom build commands that are for libs that are overridden are
-            // skipped entirely
-            if pkg.get_manifest().get_links().is_some() &&
-               cx.build_state.outputs.lock().contains_key(pkg.get_package_id()) {
-                continue
-            }
-            let (dirty, fresh, freshness) =
-                    try!(custom_build::prepare(pkg, target, cx));
-            run_custom.push((job(dirty, fresh), freshness));
-        }
-
         let work = if target.get_profile().is_doc() {
             let rustdoc = try!(rustdoc(pkg, target, cx));
             vec![(rustdoc, KindTarget)]
@@ -190,6 +191,7 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
             try!(rustc(pkg, target, cx, req))
         };
 
+        // Figure out what stage this work will go into
         let dst = match (target.is_lib(),
                          target.get_profile().is_test(),
                          target.get_profile().is_custom_build()) {
@@ -210,6 +212,45 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
             };
             dst.push((job(dirty, fresh), freshness));
         }
+
+        // If this is a custom build command, we need to not only build the
+        // script but we also need to run it. Note that this is a little nuanced
+        // because we may need to run the build script multiple times. If the
+        // package is needed in both a host and target context, we need to run
+        // it once per context.
+        if !target.get_profile().is_custom_build() { continue }
+        let mut kinds = Vec::new();
+        let requirement = targets.iter().find(|t| {
+            !t.get_profile().is_custom_build() && !t.get_profile().is_doc()
+        }).map(|&other_target| {
+            cx.get_requirement(pkg, other_target)
+        }).unwrap_or(PlatformTarget);
+        match requirement {
+            PlatformTarget => kinds.push(KindTarget),
+            PlatformPlugin => kinds.push(KindHost),
+            PlatformPluginAndTarget => {
+                kinds.push(KindTarget);
+                if cx.config.target().is_some() {
+                    kinds.push(KindHost);
+                }
+            }
+        }
+        let before = run_custom.len();
+        for &kind in kinds.iter() {
+            let key = (pkg.get_package_id().clone(), kind);
+            if pkg.get_manifest().get_links().is_some() &&
+               cx.build_state.outputs.lock().contains_key(&key) {
+                continue
+            }
+            let (dirty, fresh, freshness) =
+                    try!(custom_build::prepare(pkg, target, kind, cx));
+            run_custom.push((job(dirty, fresh), freshness));
+        }
+
+        // If no build scripts were run, no need to compile the build script!
+        if run_custom.len() == before {
+            dst.pop();
+        }
     }
 
     if targets.iter().any(|t| t.get_profile().is_custom_build()) {
@@ -342,22 +383,29 @@ fn rustc(package: &Package, target: &Target,
 
         // Prepare the native lib state (extra -L and -l flags)
         let build_state = cx.build_state.clone();
-        let mut native_lib_deps = Vec::new();
+        let mut native_lib_deps = HashSet::new();
         let current_id = package.get_package_id().clone();
         let has_custom_build = package.get_targets().iter().any(|t| {
             t.get_profile().is_custom_build()
         });
 
-        // FIXME: traverse build dependencies and add -L and -l for an
-        // transitive build deps.
-        if !target.get_profile().is_custom_build() {
-            each_dep(package, cx, |dep| {
-                if dep.get_manifest().get_links().is_some() ||
-                   (*dep.get_package_id() == current_id && has_custom_build) {
-                    native_lib_deps.push(dep.get_package_id().clone());
+        if has_custom_build && !target.get_profile().is_custom_build() {
+            native_lib_deps.insert(current_id.clone());
+        }
+        // Visit dependencies transitively to figure out what our native
+        // dependencies are (for -L and -l flags).
+        for &(pkg, _) in cx.dep_targets(package, target).iter() {
+            each_dep(pkg, cx, |dep| {
+                let has_custom_build = dep.get_targets().iter().any(|t| {
+                    t.get_profile().is_custom_build()
+                });
+                if has_custom_build {
+                    native_lib_deps.insert(dep.get_package_id().clone());
                 }
             });
         }
+        let mut native_lib_deps = native_lib_deps.into_iter().collect::<Vec<_>>();
+        native_lib_deps.sort();
 
         (proc(desc_tx: Sender<String>) {
             let mut rustc = rustc;
@@ -366,12 +414,12 @@ fn rustc(package: &Package, target: &Target,
             // arguments are for native libraries, so we process those here.
             {
                 let build_state = build_state.outputs.lock();
-                for id in native_lib_deps.iter() {
-                    let output = &(*build_state)[*id];
+                for id in native_lib_deps.into_iter() {
+                    let output = &build_state[(id.clone(), kind)];
                     for path in output.library_paths.iter() {
                         rustc = rustc.arg("-L").arg(path);
                     }
-                    if *id == current_id {
+                    if id == current_id {
                         for name in output.library_links.iter() {
                             rustc = rustc.arg("-l").arg(name.as_slice());
                         }
@@ -572,10 +620,8 @@ fn build_plugin_args(mut cmd: ProcessBuilder, cx: &Context, pkg: &Package,
         }
 
         cmd = opt(cmd, "--target", "", cx.config.target());
-        cmd = opt(cmd, "-C", "ar=", cx.config.ar().as_ref()
-                                             .map(|s| s.as_slice()));
-        cmd = opt(cmd, "-C", "linker=", cx.config.linker().as_ref()
-                                                 .map(|s| s.as_slice()));
+        cmd = opt(cmd, "-C", "ar=", cx.ar(kind));
+        cmd = opt(cmd, "-C", "linker=", cx.linker(kind));
     }
 
     return cmd;
index d65629986d36b8ac5818d2d80635f805fe378ab7..5a083bda178dd14efcd1155ce731681cb55620d4 100644 (file)
@@ -5,7 +5,7 @@ pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, rustc_ve
 pub use self::cargo_rustc::{KindTarget, KindHost, Context, LayoutProxy};
 pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget};
 pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget};
-pub use self::cargo_rustc::{BuildOutput};
+pub use self::cargo_rustc::{BuildOutput, BuildConfig, TargetConfig};
 pub use self::cargo_run::run;
 pub use self::cargo_new::{new, NewOptions};
 pub use self::cargo_doc::{doc, DocOptions};
index 398cd08985ad8386d49436b8273b1a59eec2441e..daeb85f86432e958d4505a6cb3a4e4abe50c0bbf 100644 (file)
@@ -1,5 +1,5 @@
 use std::{fmt, os, mem};
-use std::cell::{RefCell, RefMut, Ref};
+use std::cell::{RefCell, RefMut};
 use std::collections::hash_map::{HashMap, Occupied, Vacant};
 use std::io::fs::{PathExtensions, File};
 use std::string;
@@ -17,8 +17,6 @@ pub struct Config<'a> {
     shell: RefCell<&'a mut MultiShell>,
     jobs: uint,
     target: Option<string::String>,
-    linker: RefCell<Option<string::String>>,
-    ar: RefCell<Option<string::String>>,
     rustc_version: string::String,
     /// The current host and default target of rustc
     rustc_host: string::String,
@@ -42,8 +40,6 @@ impl<'a> Config<'a> {
             shell: RefCell::new(shell),
             jobs: jobs.unwrap_or(os::num_cpus()),
             target: target,
-            ar: RefCell::new(None),
-            linker: RefCell::new(None),
             rustc_version: rustc_version,
             rustc_host: rustc_host,
         })
@@ -83,17 +79,6 @@ impl<'a> Config<'a> {
         self.target.as_ref().map(|t| t.as_slice())
     }
 
-    pub fn set_ar(&self, ar: string::String) {
-        *self.ar.borrow_mut() = Some(ar);
-    }
-
-    pub fn set_linker(&self, linker: string::String) {
-        *self.linker.borrow_mut() = Some(linker);
-    }
-
-    pub fn linker(&self) -> Ref<Option<string::String>> { self.linker.borrow() }
-    pub fn ar(&self) -> Ref<Option<string::String>> { self.ar.borrow() }
-
     /// Return the output of `rustc -v verbose`
     pub fn rustc_version(&self) -> &str {
         self.rustc_version.as_slice()
index 6480ac2a9588204a4ca762742251243c8d6c2e60..58c1e004e64d2878faadabcfdb6a8e081e360df8 100644 (file)
@@ -76,7 +76,7 @@ are interpreted by Cargo and must be of the form `key=value`.
 Example output:
 
 ```
-cargo:rustc-flags=-l static:foo -L /path/to/foo
+cargo:rustc-flags=-l foo:static -L /path/to/foo
 cargo:root=/path/to/foo
 cargo:libdir=/path/to/foo/lib
 cargo:include=/path/to/foo/include
index 52211edd3a1d662674befecfc35e262bc82718f2..cd143bc9c4f105f985d4c395ffdd6d00c3490e80 100644 (file)
@@ -542,7 +542,7 @@ test!(propagation_of_l_flags {
 {running} `[..]a-[..]build-script-build[..]`
 {running} `rustc [..] --crate-name a [..]-L bar[..]-L foo[..]`
 {compiling} foo v0.5.0 (file://[..])
-{running} `rustc [..] --crate-name foo [..] -L bar[..]-L foo[..]`
+{running} `rustc [..] --crate-name foo [..] -L bar -L foo`
 ", compiling = COMPILING, running = RUNNING).as_slice()));
 })
 
index 57ed1418c1fe38b438c70ffbe72957dce7d2fe4f..71aea949b8f6941242093db9736122c3f302c837 100644 (file)
@@ -9,6 +9,7 @@ use support::{project, execs, basic_bin_manifest};
 use support::{RUNNING, COMPILING, DOCTEST, cargo_dir};
 use hamcrest::{assert_that, existing_file};
 use cargo::util::process;
+use cargo::ops::rustc_version;
 
 fn setup() {
 }
@@ -516,3 +517,84 @@ test!(cross_with_a_build_script {
    dir = p.root().display(), sep = path::SEP).as_slice()));
 })
 
+test!(build_script_needed_for_host_and_target {
+    if disabled() { return }
+
+    let target = alternate();
+    let (_, host) = rustc_version().unwrap();
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.0.0"
+            authors = []
+            build = 'build.rs'
+
+            [dependencies.d1]
+            path = "d1"
+            [build-dependencies.d2]
+            path = "d2"
+        "#)
+
+        .file("build.rs", r#"
+            extern crate d2;
+            fn main() { d2::d2(); }
+        "#)
+        .file("src/main.rs", "
+            extern crate d1;
+            fn main() { d1::d1(); }
+        ")
+        .file("d1/Cargo.toml", r#"
+            [package]
+            name = "d1"
+            version = "0.0.0"
+            authors = []
+            build = 'build.rs'
+        "#)
+        .file("d1/src/lib.rs", "
+            pub fn d1() {}
+        ")
+        .file("d1/build.rs", r#"
+            use std::os;
+            fn main() {
+                let target = os::getenv("TARGET").unwrap();
+                println!("cargo:rustc-flags=-L /path/to/{}", target);
+            }
+        "#)
+        .file("d2/Cargo.toml", r#"
+            [package]
+            name = "d2"
+            version = "0.0.0"
+            authors = []
+
+            [dependencies.d1]
+            path = "../d1"
+        "#)
+        .file("d2/src/lib.rs", "
+            extern crate d1;
+            pub fn d2() { d1::d1(); }
+        ");
+
+    assert_that(p.cargo_process("build").arg("--target").arg(&target).arg("-v"),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{compiling} d1 v0.0.0 (file://{dir})
+{running} `rustc build.rs [..] --out-dir {dir}{sep}target{sep}build{sep}d1-[..]`
+{running} `{dir}{sep}target{sep}build{sep}d1-[..]build-script-build`
+{running} `{dir}{sep}target{sep}build{sep}d1-[..]build-script-build`
+{running} `rustc {dir}{sep}d1{sep}src{sep}lib.rs [..] --target {target} [..] \
+           -L /path/to/{target}`
+{running} `rustc {dir}{sep}d1{sep}src{sep}lib.rs [..] \
+           -L /path/to/{host}`
+{compiling} d2 v0.0.0 (file://{dir})
+{running} `rustc {dir}{sep}d2{sep}src{sep}lib.rs [..] \
+           -L /path/to/{host}`
+{compiling} foo v0.0.0 (file://{dir})
+{running} `rustc build.rs [..] --out-dir {dir}{sep}target{sep}build{sep}foo-[..] \
+           -L /path/to/{host}`
+{running} `{dir}{sep}target{sep}build{sep}foo-[..]build-script-build`
+{running} `rustc {dir}{sep}src{sep}main.rs [..] --target {target} [..] \
+           -L /path/to/{target}`
+", compiling = COMPILING, running = RUNNING, target = target, host = host,
+   dir = p.root().display(), sep = path::SEP).as_slice()));
+})